Исследование рынка заведений общепита в Москве

Оглавление

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
import seaborn as sns
import math
from IPython.display import display
from scipy import stats as st
import requests
import re
from bs4 import BeautifulSoup

Шаг №1. Загрузим данные и подготовим их к анализу

Загрузим датасет rest_data, выведем общую информацию и первые десять строк на экран. Проверим на дубликаты.

In [2]:
rest_data = pd.read_csv('/datasets/rest_data.csv')
In [3]:
rest_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 15366 entries, 0 to 15365
Data columns (total 6 columns):
id             15366 non-null int64
object_name    15366 non-null object
chain          15366 non-null object
object_type    15366 non-null object
address        15366 non-null object
number         15366 non-null int64
dtypes: int64(2), object(4)
memory usage: 720.4+ KB
In [4]:
rest_data.head(10)
Out[4]:
id object_name chain object_type address number
0 151635 СМЕТАНА нет кафе город Москва, улица Егора Абакумова, дом 9 48
1 77874 Родник нет кафе город Москва, улица Талалихина, дом 2/1, корпус 1 35
2 24309 Кафе «Академия» нет кафе город Москва, Абельмановская улица, дом 6 95
3 21894 ПИЦЦЕТОРИЯ да кафе город Москва, Абрамцевская улица, дом 1 40
4 119365 Кафе «Вишневая метель» нет кафе город Москва, Абрамцевская улица, дом 9, корпус 1 50
5 27429 СТОЛ. ПРИ ГОУ СОШ № 1051 нет столовая город Москва, Абрамцевская улица, дом 15, корп... 240
6 148815 Брусника да кафе город Москва, переулок Сивцев Вражек, дом 6/2 10
7 20957 Буфет МТУСИ нет столовая город Москва, Авиамоторная улица, дом 8, строе... 90
8 20958 КПФ СЕМЬЯ-1 нет столовая город Москва, Авиамоторная улица, дом 8, строе... 150
9 28858 Столовая МТУСИ нет столовая город Москва, Авиамоторная улица, дом 8, строе... 120
In [5]:
rest_data.duplicated().sum()
Out[5]:
0

Дататасет состоит из 6 столбцов и 15366 строк. Названия столбцов не требуют изменений. Типы данных корректны. Пропуски, дубликаты отсутствуют.

Посчитаем количество частичных дубликатов по столбцам object_name, chain, object_type, address.

In [6]:
rest_data[['object_name', 'chain', 'object_type', 'address']].duplicated().sum()
Out[6]:
170

Частичные дубликаты составляют 1% датасета, удалим их.

In [7]:
rest_data = rest_data.drop_duplicates(subset=['object_name', 'chain', 'object_type', 'address'])

Шаг №2. Анализ данных

Исследуем соотношение видов объектов общественного питания по количеству.

Сгруппируем таблицу rest_data по типам объекта общественного питания и посчитаем количество. Сбросим индексы методом reset_index(), отсортируем по убыванию.

In [8]:
catering_types = rest_data.groupby('object_type').agg({'id': 'count'}).reset_index().sort_values(
    by='id', ascending=False)
catering_types
Out[8]:
object_type id
3 кафе 6017
8 столовая 2575
7 ресторан 2276
6 предприятие быстрого обслуживания 1891
0 бар 853
1 буфет 571
4 кафетерий 393
2 закусочная 348
5 магазин (отдел кулинарии) 272

Построим графики.

In [9]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Cоотношение видов объектов общественного питания по количеству')
sns.barplot(x="object_type", y="id", data=catering_types) 
ax = plt.gca()
ax.set_ylabel('Количество объектов')
ax.set_xlabel('Вид объекта')
ax.set_xticklabels(catering_types['object_type'], rotation = 90, verticalalignment = 'top');
In [10]:
# x="object_type", y="id", data=catering_types
fig = px.bar(catering_types.sort_values(by='id'), x='id', y='object_type', orientation='h', \
             labels={'id':'Количество объектов', 'object_type': 'Вид объекта'})
fig.update_layout(title_text='Cоотношение видов объектов общественного питания по количеству', yaxis_showticklabels=False)
fig.show()
In [11]:
plt.figure(figsize=(12, 12))
plt.pie(catering_types['id'])
plt.legend(catering_types['object_type'])
plt.title('Соотношение видов объектов общественного питания по количеству');
In [12]:
plt.figure(figsize=(20, 10))
plt.title('Соотношение видов объектов общественного питания по количеству')
plt.subplot(1, 2, 1)
plt.grid(True)
sns.barplot(x="id", y="object_type", data=catering_types, orient='h') 
ax = plt.gca()
ax.set_xlabel('Количество объектов')
ax.set_ylabel('Вид объекта')
plt.subplot(1, 2, 2)
plt.pie(catering_types['id'])
plt.legend(catering_types['object_type']);

Вывод: самыми популярными объектами общественного питания в Москве являются кафе, столовые, рестораны и фастфуд. Наименее популярными — кафетерии, закусочные и отделы кулинарии в магазинах.

Исследуем соотношение сетевых и несетевых заведений по количеству.

Сгруппируем таблицу rest_data по сетевым и несетевым заведениям и посчитаем количество. Сбросим индексы методом reset_index(), отсортируем по убыванию.

In [13]:
chain_number = rest_data.groupby('chain').agg({'id': 'count'}).reset_index().sort_values(
    by='id', ascending=False)
chain_number
Out[13]:
chain id
1 нет 12245
0 да 2951

Построим график.

In [14]:
plt.figure(figsize=(7, 7))
plt.grid(True)
plt.title('Соотношение сетевых и несетевых заведений по количеству')
sns.barplot(x="chain", y="id", data=chain_number) 
ax = plt.gca()
ax.set_ylabel('Количество объектов общественного питания')
ax.set_xlabel('')
ax.set_xticklabels(['несетевое', 'сетевое'])

totals = []
for i in ax.patches:
    totals.append(i.get_height())
total = sum(totals)

for i in ax.patches:
    ax.text(i.get_x()+.3, i.get_height()-800, str(round((i.get_height()/total)*100, 2))+' %', fontsize=14);

Вывод: несетевых заведений в Москве в четыре раза больше, чем сетевых.

Посчитаем, для какого вида объекта общественного питания характерно сетевое распространение.

Отфильтруем таблицу rest_data по сетевым заведениям, сгруппируем по типам объекта общественного питания и посчитаем количество. Сбросим индексы методом reset_index(), отсортируем по убыванию.

In [15]:
catering_chains = rest_data[rest_data['chain'] == 'да'].groupby('object_type').agg({'id': 'count'}).reset_index().\
sort_values(by='id', ascending=False)
catering_chains
Out[15]:
object_type id
3 кафе 1389
6 предприятие быстрого обслуживания 784
7 ресторан 542
5 магазин (отдел кулинарии) 78
2 закусочная 56
4 кафетерий 51
0 бар 37
1 буфет 11
8 столовая 3

Построим график.

In [16]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Распределение сетевых заведений по количеству')
sns.barplot(x="object_type", y="id", data=catering_chains) 
ax = plt.gca()
ax.set_ylabel('Количество сетевых заведений')
ax.set_xlabel('Вид объекта')
ax.set_xticklabels(catering_chains['object_type'], rotation = 90, verticalalignment = 'top');

Вывод: больше всего сетей кафе, фастфудов, ресторанов.

In [17]:
chain_types = rest_data.groupby(['chain','object_type']).agg({'id': 'count'}).reset_index()
chain_types['chain'] = chain_types['chain'].replace({'да': 'сетевое', 'нет': 'несетевое'})
chain_types
Out[17]:
chain object_type id
0 сетевое бар 37
1 сетевое буфет 11
2 сетевое закусочная 56
3 сетевое кафе 1389
4 сетевое кафетерий 51
5 сетевое магазин (отдел кулинарии) 78
6 сетевое предприятие быстрого обслуживания 784
7 сетевое ресторан 542
8 сетевое столовая 3
9 несетевое бар 816
10 несетевое буфет 560
11 несетевое закусочная 292
12 несетевое кафе 4628
13 несетевое кафетерий 342
14 несетевое магазин (отдел кулинарии) 194
15 несетевое предприятие быстрого обслуживания 1107
16 несетевое ресторан 1734
17 несетевое столовая 2572

Построим график.

In [18]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Cоотношение видов сетевых и несетевых заведений общественного питания по количеству')
sns.barplot(x="id", y="object_type", hue='chain', data=chain_types, orient='h') 
ax = plt.gca()
ax.set_xlabel('Количество объектов')
ax.set_ylabel('Вид объекта');

Создадим таблицу chain_ratio с количеством сетевых заведений. Добавим столбец с количеством несетевых заведений. Найдём соотношение и отсортируем по убыванию.

In [19]:
chain_ratio = chain_types[chain_types['chain'] == 'сетевое']
chain_ratio['id_2'] = chain_types[chain_types['chain'] == 'несетевое'].reset_index()['id']
chain_ratio['ratio'] = chain_ratio['id'] / chain_ratio['id_2']
chain_ratio = chain_ratio.sort_values(by='ratio', ascending=False)
chain_ratio
/opt/conda/lib/python3.7/site-packages/ipykernel_launcher.py:2: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

/opt/conda/lib/python3.7/site-packages/ipykernel_launcher.py:3: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

Out[19]:
chain object_type id id_2 ratio
6 сетевое предприятие быстрого обслуживания 784 1107 0.708220
5 сетевое магазин (отдел кулинарии) 78 194 0.402062
7 сетевое ресторан 542 1734 0.312572
3 сетевое кафе 1389 4628 0.300130
2 сетевое закусочная 56 292 0.191781
4 сетевое кафетерий 51 342 0.149123
0 сетевое бар 37 816 0.045343
1 сетевое буфет 11 560 0.019643
8 сетевое столовая 3 2572 0.001166

Построим график.

In [20]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Cоотношение видов сетевых и несетевых заведений общественного питания по количеству')
sns.barplot(x="ratio", y="object_type", data=chain_ratio, orient='h') 
ax = plt.gca()
ax.set_xlabel('Cоотношение сетевых и несетевых заведений')
ax.set_ylabel('Вид объекта');

Вывод: больше всего сетей фастфудов, кулинарий, ресторанов, кафе.

Посчитаем, что характерно для сетевых заведений: много заведений с небольшим числом посадочных мест в каждом или мало заведений с большим количеством посадочных мест?

Построим гистограмму.

In [21]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Распределение сетевых заведений по количеству мест')
ax = plt.gca()
ax.set_xlabel('Количество мест')
ax.set_ylabel('Количество сетевых заведений')
rest_data[rest_data['chain'] == 'да']['number'].hist(figsize=(12, 7), bins=100);

Вывод: для большинства сетевых заведений характерно небольшое число посадочных мест (до 50).

Для каждого вида объекта общественного питания посчитаем среднее количество посадочных мест.

Сгруппируем таблицу rest_data по типам объекта общественного питания и посчитаем среднее количество посадочных мест. Сбросим индексы методом reset_index(), отсортируем по убыванию.

In [22]:
catering_types_number = rest_data.groupby('object_type').agg({'number': 'mean'}).reset_index().sort_values(
    by='number', ascending=False)
catering_types_number
Out[22]:
object_type number
8 столовая 130.367767
7 ресторан 97.031195
1 буфет 51.572680
0 бар 43.602579
3 кафе 39.875187
6 предприятие быстрого обслуживания 20.786885
4 кафетерий 9.221374
2 закусочная 7.663793
5 магазин (отдел кулинарии) 5.610294
In [23]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Распределение среднего количества посадочных мест по типам объектов общественного питания')
sns.barplot(x="object_type", y="number", data=catering_types_number) 
ax = plt.gca()
ax.set_ylabel('Cреднее количество посадочных мест')
ax.set_xlabel('Тип объекта общественного питания')
ax.set_xticklabels(catering_types_number['object_type'], rotation = 90, verticalalignment = 'top');
In [24]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Распределение среднего количества посадочных мест по типам объектов общественного питания')
sns.stripplot(x="number", y="object_type", data=rest_data, orient='h') 
ax = plt.gca()
ax.set_xlabel('Cреднее количество посадочных мест')
ax.set_ylabel('Тип объекта общественного питания');

Вывод: в среднем самое большое количество посадочных мест в столовых и ресторанах.

Создадим таблицу rest_data_chain с сетевыми заведениями. Обработаем столбец object_name. Приведём символы к нижнему регистру методом lower(), заменим ё на е методом replace().

In [25]:
rest_data_chain = rest_data[rest_data['chain'] == 'да'].reset_index()
rest_data_chain['object_name'] = rest_data_chain['object_name'].str.lower()
rest_data_chain['object_name'] = rest_data_chain['object_name'].str.replace('ё','е')

Максимально очистим столбец object_name от лишних подстрок. Создадим массив excess_names.

In [26]:
excess_names = ['«', '»', 'кафе ', 'бар ', 'закусочная ', 'кондитерская ', 'кондитерская-пекарня', 'кофейня ', 'пбо ', \
                'пицца ', 'пиццерия ', 'предприятие быстрого обслуживания ', 'ресторан ', 'быстрого питания ', 'ресторан']

В цикле по массиву excess_names удалим подстроки в столбце object_name.

In [27]:
for i in excess_names:
    rest_data_chain['object_name'] = rest_data_chain['object_name'].str.replace(i, '')

Заменим наиболее очевидные повторы.

In [28]:
rest_data_chain['object_name'] = rest_data_chain['object_name'].str.replace('бургер бинг burger king','burger king')
In [29]:
rest_data_chain['object_name'] = rest_data_chain['object_name'].str.replace('бургер кинг','burger king')
In [30]:
rest_data_chain['object_name'] = rest_data_chain['object_name'].str.replace('сабвей','subway')
In [31]:
rest_data_chain['object_name'] = rest_data_chain['object_name'].str.replace('старбакс starbucks','starbucks')
In [32]:
rest_data_chain['object_name'] = rest_data_chain['object_name'].str.replace('старбакс','starbucks')

Делаем выборки для проверки.

In [33]:
rest_data_chain.iloc[:, 1:7].sample(5)
Out[33]:
id object_name chain object_type address number
2936 206341 мята lounge да кафе город Москва, Салтыковская улица, дом 7Г 100
2232 189902 burger king да предприятие быстрого обслуживания город Москва, бульвар Дмитрия Донского, дом 1 15
1124 91158 маки-маки да кафе город Москва, Профсоюзная улица, дом 129А 100
2883 211257 милти да предприятие быстрого обслуживания город Москва, Новогиреевская улица, дом 11/36,... 0
2949 209264 шоколадница да кафе город Москва, улица Земляной Вал, дом 33 10

Сгруппируем таблицу rest_data_chain_group по названиям сетей, посчитаем количество заведений и среднее число посадочных мест. Сбросим индексы и отсортируем по числу заведений.

In [34]:
rest_data_chain_group = rest_data_chain.groupby('object_name').agg({'id': 'count','number': 'mean'}).reset_index()
rest_data_chain_group = rest_data_chain_group.sort_values(by='id', ascending=False)
rest_data_chain_group.head()
Out[34]:
object_name id number
409 шоколадница 180 58.111111
28 kfc 176 54.988636
241 макдоналдс 169 88.218935
8 burger king 150 46.493333
362 теремок 109 27.073394
In [35]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Точечная диаграмма среднего числа посадочных мест на количество заведений в сети')
sns.scatterplot(data=rest_data_chain_group, x="id", y="number")
ax = plt.gca()
ax.set_xlabel('Количество заведений в сети')
ax.set_ylabel('Среднее число посадочных мест');

Вывод: для сетевых заведений характерно малое число заведений с большим количеством посадочных мест.

Выделим в отдельный столбец информацию об улице из столбца address.

Продублируем столбец адреса.

In [36]:
rest_data['address_fixed'] = rest_data['address']

Удалим подстроку город Москва, в столбце address_fixed методом str.replace().

In [37]:
rest_data['address_fixed'] = rest_data['address_fixed'].str.replace('город Москва, ', '')

Выделим улицу, разделив строку адреса по подстроке , дом на две части методом split(). Выберем первый элемент массива. Например:

In [38]:
'улица Егора Абакумова, дом 9'.split(', дом ')[0]
Out[38]:
'улица Егора Абакумова'

Однако среди адресов встречаются не только "дома", но и "корпуса", "владения", "домовладения", которые функция split() не затронет. Например:

In [39]:
'9-я Парковая улица, владение 61А, строение 1'.split(', дом ')[0]
Out[39]:
'9-я Парковая улица, владение 61А, строение 1'

При помощи лямбда-функции разделим каждую строку столбца address_fixed по подстрокам , дом, , корпус, , владение, , домовладение и сохраним первый элемент массива.

In [40]:
rest_data['address_fixed'] = rest_data['address_fixed'].apply(lambda address: address.split(', дом ')[0])
In [41]:
rest_data['address_fixed'] = rest_data['address_fixed'].apply(lambda address: address.split(', корпус ')[0])
In [42]:
rest_data['address_fixed'] = rest_data['address_fixed'].apply(lambda address: address.split(', владение ')[0])
In [43]:
rest_data['address_fixed'] = rest_data['address_fixed'].apply(lambda address: address.split(', домовладение ')[0])

Построим график топ-10 улиц по количеству объектов общественного питания.

Сохраним в переменную top10_adresses количество уникальных значений адресов методом value_counts(), выберем первые десять значений, сбросим индексы методом reset_index().

In [44]:
top10_adresses = rest_data['address_fixed'].value_counts().head(10).reset_index()
top10_adresses
Out[44]:
index address_fixed
0 проспект Мира 197
1 Профсоюзная улица 181
2 Ленинградский проспект 172
3 Пресненская набережная 165
4 Варшавское шоссе 161
5 Ленинский проспект 148
6 город Зеленоград 128
7 проспект Вернадского 127
8 Кутузовский проспект 114
9 Каширское шоссе 110

Построим график.

In [45]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Топ-10 улиц по количеству объектов общественного питания')
sns.barplot(x="index", y="address_fixed", data=top10_adresses)
ax = plt.gca()
ax.set_ylabel('Количество объектов общественного питания')
ax.set_xlabel('Улицы')
ax.set_xticklabels(top10_adresses['index'], rotation = 90, verticalalignment = 'top');

Для использования внешней информации распарсим сайт http://mosopen.ru/regions и поставим в соответствие каждой улице свой административный округ.

Выберем Зеленоградский округ

In [46]:
URL = 'http://mosopen.ru/district/zelao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [47]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки
In [48]:
content.append(['город Зеленоград']) # Добавим город Зеленоград

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [49]:
zelao_streets = pd.DataFrame(content)
zelao_streets.columns = ['address_fixed_2']
zelao_streets['address_fixed_2'] = zelao_streets['address_fixed_2'].str.lower()
zelao_streets['address_fixed_2'] = zelao_streets['address_fixed_2'].str.replace('ё','е')
zelao_streets['district_zelao'] = 'ЗелАО'
zelao_streets.head()
Out[49]:
address_fixed_2 district_zelao
0 1 мая, улица ЗелАО
1 алабушевская улица ЗелАО
2 александровка, улица ЗелАО
3 андреевка, улица ЗелАО
4 березовая аллея (г. зеленоград) ЗелАО

Выберем Северо-Западный округ

In [50]:
URL = 'http://mosopen.ru/district/szao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [51]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [52]:
szao_streets = pd.DataFrame(content)
szao_streets.columns = ['address_fixed_2']
szao_streets['address_fixed_2'] = szao_streets['address_fixed_2'].str.lower()
szao_streets['address_fixed_2'] = szao_streets['address_fixed_2'].str.replace('ё','е')
szao_streets['district_szao'] = 'СЗАО'
szao_streets.head()
Out[52]:
address_fixed_2 district_szao
0 авиационная улица СЗАО
1 академика бочвара, улица СЗАО
2 академика курчатова, площадь СЗАО
3 академика курчатова, улица СЗАО
4 алешкинский проезд СЗАО

Выберем Западный округ

In [53]:
URL = 'http://mosopen.ru/district/zao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [54]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [55]:
zao_streets = pd.DataFrame(content)
zao_streets.columns = ['address_fixed_2']
zao_streets['address_fixed_2'] = zao_streets['address_fixed_2'].str.lower()
zao_streets['address_fixed_2'] = zao_streets['address_fixed_2'].str.replace('ё','е')
zao_streets['district_zao'] = 'ЗАО'
zao_streets.head()
Out[55]:
address_fixed_2 district_zao
0 1812 года, улица ЗАО
1 50 лет октября, улица ЗАО
2 8 марта, улица (пос. внуково) ЗАО
3 авиаторов, улица ЗАО
4 академика анохина, улица ЗАО

Выберем Юго-Западный округ

In [56]:
URL = 'http://mosopen.ru/district/uzao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [57]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [58]:
uzao_streets = pd.DataFrame(content)
uzao_streets.columns = ['address_fixed_2']
uzao_streets['address_fixed_2'] = uzao_streets['address_fixed_2'].str.lower()
uzao_streets['address_fixed_2'] = uzao_streets['address_fixed_2'].str.replace('ё','е')
uzao_streets['district_uzao'] = 'ЮЗАО'
uzao_streets.head()
Out[58]:
address_fixed_2 district_uzao
0 60-летия октября, проспект ЮЗАО
1 60-летия ссср, площадь ЮЗАО
2 8 марта, улица (пос. липки) ЮЗАО
3 адмирала лазарева, улица ЮЗАО
4 адмирала руднева, улица ЮЗАО

Выберем Южный округ

In [59]:
URL = 'http://mosopen.ru/district/uao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [60]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [61]:
uao_streets = pd.DataFrame(content)
uao_streets.columns = ['address_fixed_2']
uao_streets['address_fixed_2'] = uao_streets['address_fixed_2'].str.lower()
uao_streets['address_fixed_2'] = uao_streets['address_fixed_2'].str.replace('ё','е')
uao_streets['district_uao'] = 'ЮАО'
uao_streets.head()
Out[61]:
address_fixed_2 district_uao
0 автозаводская площадь ЮАО
1 автозаводская улица ЮАО
2 автозаводский 1-й, проезд ЮАО
3 автозаводский 2-й, проезд ЮАО
4 автозаводский 3-й, проезд ЮАО

Выберем Юго-Восточный округ

In [62]:
URL = 'http://mosopen.ru/district/uvao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [63]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [64]:
uvao_streets = pd.DataFrame(content)
uvao_streets.columns = ['address_fixed_2']
uvao_streets['address_fixed_2'] = uvao_streets['address_fixed_2'].str.lower()
uvao_streets['address_fixed_2'] = uvao_streets['address_fixed_2'].str.replace('ё','е')
uvao_streets['district_uvao'] = 'ЮВАО'
uvao_streets.head()
Out[64]:
address_fixed_2 district_uvao
0 40 лет октября, проспект ЮВАО
1 авиаконструктора миля, улица ЮВАО
2 авиамоторная улица ЮВАО
3 автомобильный проезд ЮВАО
4 академика скрябина, улица ЮВАО

Выберем Восточный округ

In [65]:
URL = 'http://mosopen.ru/district/vao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [66]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [67]:
vao_streets = pd.DataFrame(content)
vao_streets.columns = ['address_fixed_2']
vao_streets['address_fixed_2'] = vao_streets['address_fixed_2'].str.lower()
vao_streets['address_fixed_2'] = vao_streets['address_fixed_2'].str.replace('ё','е')
vao_streets['district_vao'] = 'ВАО'
vao_streets.head()
Out[67]:
address_fixed_2 district_vao
0 8 марта, улица (косино-ухтомский) ВАО
1 9 мая, улица ВАО
2 абрамцевская просека ВАО
3 акулово, поселок ВАО
4 алексея дикого, улица ВАО

Выберем Северо-Восточный округ

In [68]:
URL = 'http://mosopen.ru/district/svao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [69]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [70]:
svao_streets = pd.DataFrame(content)
svao_streets.columns = ['address_fixed_2']
svao_streets['address_fixed_2'] = svao_streets['address_fixed_2'].str.lower()
svao_streets['address_fixed_2'] = svao_streets['address_fixed_2'].str.replace('ё','е')
svao_streets['district_svao'] = 'СВАО'
svao_streets.head()
Out[70]:
address_fixed_2 district_svao
0 абрамцевская улица СВАО
1 академика комарова, улица СВАО
2 академика королева, улица СВАО
3 академика люльки, площадь СВАО
4 алтуфьевское шоссе СВАО

Выберем Северный округ

In [71]:
URL = 'http://mosopen.ru/district/sao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [72]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [73]:
sao_streets = pd.DataFrame(content)
sao_streets.columns = ['address_fixed_2']
sao_streets['address_fixed_2'] = sao_streets['address_fixed_2'].str.lower()
sao_streets['address_fixed_2'] = sao_streets['address_fixed_2'].str.replace('ё','е')
sao_streets['district_sao'] = 'САО'
sao_streets.head()
Out[73]:
address_fixed_2 district_sao
0 8 марта 1-я, улица САО
1 8 марта 4-я, улица САО
2 8 марта, улица САО
3 800-летия москвы, переулок САО
4 800-летия москвы, улица САО

Выберем Центральный округ

In [74]:
URL = 'http://mosopen.ru/district/cao/streets'
req = requests.get(URL)

soup = BeautifulSoup(req.text, 'lxml')

table = soup.find('div', attrs = {'class':'double_block clearfix'}) # Класс, где хранятся данные
In [75]:
content=[] # Список, в котором будут храниться данные
for row in table.find_all('li'): # Каждая строка обрамляется тегом li, необходимо пробежаться в цикле по всем строкам
    content.append([element.text for element in row.find_all('a')]) # И убрать ссылки

Объединим данные в датафрейм, изменим заголовок, приведём всё к нижнему регистру методом lower(), заменим ё на е методом replace(), добавим название округа.

In [76]:
cao_streets = pd.DataFrame(content)
cao_streets.columns = ['address_fixed_2']
cao_streets['address_fixed_2'] = cao_streets['address_fixed_2'].str.lower()
cao_streets['address_fixed_2'] = cao_streets['address_fixed_2'].str.replace('ё','е')
cao_streets['district_cao'] = 'ЦАО'
cao_streets.head()
Out[76]:
address_fixed_2 district_cao
0 10-летия октября, улица ЦАО
1 1905 года, улица ЦАО
2 абельмановская застава, площадь ЦАО
3 абельмановская улица ЦАО
4 абрикосовский переулок ЦАО

Объединим данные

Создадим столбец address_fixed_2 в таблице rest_data. Приведём всё к нижнему регистру методом lower() и заменим ё на е методом replace().

In [77]:
rest_data['address_fixed_2'] = rest_data['address_fixed']
rest_data['address_fixed_2'] = rest_data['address_fixed_2'].str.lower()
rest_data['address_fixed_2'] = rest_data['address_fixed_2'].str.replace('ё','е')

Для объединения данных максимально очистим столбец address_fixed_2 от лишних подстрок. Создадим массив excess_adress со всеми возможными комбинациями.

In [78]:
excess_adress = [', улица', ' улица', 'улица ', \
                 ', проспект', ' проспект', 'проспект ', \
                 ', проезд', ' проезд', 'проезд ', \
                 ', бульвар', ' бульвар', 'бульвар ', \
                 ', площадь', ' площадь', 'площадь ', \
                 ', линия', ' линия', 'линия ', \
                 ', переулок', ' переулок', 'переулок ', \
                 ', квартал', ' квартал', 'квартал ', \
                 ', шоссе', ' шоссе', 'шоссе ', \
                 ', просека', ' просека', 'просека ', ', просек', ' просек', 'просек ', \
                 ', аллея', ' аллея', 'аллея ', \
                 ', территория', ' территория', 'территория ', \
                 ', тупик', ' тупик', 'тупик ', \
                 ' 11-й', '11-й ', ' 11-я', '11-я ', ' 12-й', '12-й ', ' 12-я', '12-я ', \
                 ' 13-й', '13-й ', ' 13-я', '13-я ', ' 14-й', '14-й ', ' 14-я', '14-я ', \
                 ' 15-й', '15-й ', ' 15-я', '15-я ', ' 16-й', '16-й ', ' 16-я', '16-я ', \
                 ' 17-й', '17-й ', ' 17-я', '17-я ', ' 17-й', '17-й ', ' 17-я', '17-я ', \
                 ' 19-й', '19-й ', ' 19-я', '19-я ', ' 20-й', '20-й ', ' 20-я', '20-я ', \
                 ' 1-й', '1-й ', ' 1-я', '1-я ', ' 2-й', '2-й ', ' 2-я', '2-я ', ' 3-й', '3-й ', ' 3-я', '3-я ', \
                 ' 4-й', '4-й ', ' 4-я', '4-я ', ' 5-й', '5-й ', ' 5-я', '5-я ', ' 6-й', '6-й ', ' 6-я', '6-я ', \
                 ' 7-й', '7-й ', ' 7-я', '7-я ', ' 8-й', '8-й ', ' 8-я', '8-я ', ' 9-й', '9-й ', ' 9-я', '9-я ']

В цикле по массиву excess_adress удалим подстроки в столбце address_fixed_2 каждой таблицы.

In [79]:
for i in excess_adress:
    rest_data['address_fixed_2'] = rest_data['address_fixed_2'].str.replace(i, '')
    zelao_streets['address_fixed_2'] = zelao_streets['address_fixed_2'].str.replace(i, '')
    szao_streets['address_fixed_2'] = szao_streets['address_fixed_2'].str.replace(i, '')
    zao_streets['address_fixed_2'] = zao_streets['address_fixed_2'].str.replace(i, '')
    uzao_streets['address_fixed_2'] = uzao_streets['address_fixed_2'].str.replace(i, '')
    uao_streets['address_fixed_2'] = uao_streets['address_fixed_2'].str.replace(i, '')
    uvao_streets['address_fixed_2'] = uvao_streets['address_fixed_2'].str.replace(i, '')
    vao_streets['address_fixed_2'] = vao_streets['address_fixed_2'].str.replace(i, '')
    svao_streets['address_fixed_2'] = svao_streets['address_fixed_2'].str.replace(i, '')
    sao_streets['address_fixed_2'] = sao_streets['address_fixed_2'].str.replace(i, '')
    cao_streets['address_fixed_2'] = cao_streets['address_fixed_2'].str.replace(i, '')

Наконец объединим данные методом merge() по столбцу address_fixed_2.

In [80]:
rest_data = rest_data.merge(zelao_streets, on='address_fixed_2', how='left')\
.merge(szao_streets, on='address_fixed_2', how='left')\
.merge(zao_streets, on='address_fixed_2', how='left')\
.merge(uzao_streets, on='address_fixed_2', how='left')\
.merge(uao_streets, on='address_fixed_2', how='left')\
.merge(uvao_streets, on='address_fixed_2', how='left')\
.merge(vao_streets, on='address_fixed_2', how='left')\
.merge(svao_streets, on='address_fixed_2', how='left')\
.merge(sao_streets, on='address_fixed_2', how='left')\
.merge(cao_streets, on='address_fixed_2', how='left')

Удалим дубликаты.

In [81]:
rest_data = rest_data.drop_duplicates()

Делаем выборки для проверки.

In [82]:
rest_data.iloc[:, 7:18].sample(5)
Out[82]:
address_fixed_2 district_zelao district_szao district_zao district_uzao district_uao district_uvao district_vao district_svao district_sao district_cao
18592 пресненская набережная NaN NaN NaN NaN NaN NaN NaN NaN NaN ЦАО
10418 садовническая NaN NaN NaN NaN NaN NaN NaN NaN NaN ЦАО
6162 новый арбат NaN NaN NaN NaN NaN NaN NaN NaN NaN ЦАО
11386 цветной NaN NaN NaN NaN NaN NaN NaN NaN NaN ЦАО
3548 малая дмитровка NaN NaN NaN NaN NaN NaN NaN NaN NaN ЦАО

Примечание: некоторые улицы, например, Талалихина находятся на территории сразу двух округов: Центрального и Юго-Восточного. Ленинский проспект раскинулся сразу на четыре округа: Юго-Западный, Южный, Западный и Центральный округа. Бывает, что улицы со схожим названием после обработки оказываются сразу в двух округах. Например, Парковая улица из Западного округа и 1-я Парковая улица из Восточного округа.

Посчитаем количество улиц, расположенных на территории каждого округа, сбросим индексы, изменим заголовки, добавим названия округов и отсортируем по количеству улиц.

In [83]:
top10_districts = rest_data.iloc[:, 8:18].count().reset_index()
top10_districts.columns = ['id', 'streets']
top10_districts['district'] = pd.Series(['ЗелАО', 'СЗАО', 'ЗАО', 'ЮЗАО', 'ЮАО', 'ЮВАО', 'ВАО', 'СВАО', 'САО', 'ЦАО'])
top10_districts = top10_districts.sort_values(by='streets', ascending=False)
top10_districts
Out[83]:
id streets district
9 district_cao 4940 ЦАО
4 district_uao 2339 ЮАО
2 district_zao 1713 ЗАО
3 district_uzao 1632 ЮЗАО
7 district_svao 1564 СВАО
8 district_sao 1542 САО
6 district_vao 1290 ВАО
5 district_uvao 1127 ЮВАО
1 district_szao 787 СЗАО
0 district_zelao 307 ЗелАО

Построим график.

In [84]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Топ-10 округов по количеству объектов общественного питания')
sns.barplot(x="district", y="streets", data=top10_districts)
ax = plt.gca()
ax.set_ylabel('Количество объектов общественного питания')
ax.set_xlabel('Округа');

Посчитаем количество улиц, оказавшихся за пределами округов. Просуммируем количество ненулевых элементов по строкам при помощи функции sum() с параметром axis=1 и посчитаем количество уникальных элементов.

In [85]:
rest_data.iloc[:, 8:18].notnull().sum(axis=1).value_counts()
Out[85]:
1    12076
2     1901
0      814
3      257
4      148
dtype: int64

Улиц вне округа — 814. Улиц, расположенных более чем на территории более чем двух округов (в т.ч. ошибочно), — 2306.

Посмотрим на улицы вне округов. Объединим в запросе пропуски и сделаем выборку методом sample.

In [86]:
rest_data.query('\
                district_zelao.isnull() and \
                district_szao.isnull() and \
                district_zao.isnull() and \
                district_uzao.isnull() and \
                district_uao.isnull() and \
                district_uvao.isnull() and \
                district_vao.isnull() and \
                district_svao.isnull() and \
                district_sao.isnull() and \
                district_cao.isnull() \
').sample(3)
Out[86]:
id object_name chain object_type address number address_fixed address_fixed_2 district_zelao district_szao district_zao district_uzao district_uao district_uvao district_vao district_svao district_sao district_cao
13593 175237 Кафе «Pizza express 24» нет кафе город Москва, город Троицк, Октябрьский проспе... 16 город Троицк, Октябрьский проспект город троицк, октябрьский NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2336 142444 Кафе «Берёзка» нет кафе город Москва, микрорайон Северное Чертаново, д... 47 микрорайон Северное Чертаново микрорайон северное чертаново NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
12900 172231 Кофейня «Мой кофе» нет кафе город Москва, город Троицк, улица Полковника М... 10 город Троицк, улица Полковника Милиции Курочкина город троицк полковника милиции курочкина NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

Видно, что это Подмосковье: Зеленоград, Московский, Троицк, Щербинка, а также поселения и микрорайоны.

Примечание: некоторые улицы, например, академика Ландау или Маршала Прошлякова отсутствуют на сайте http://mosopen.ru.

Найдём, в каких районах Москвы находятся топ-10 улиц по количеству объектов общественного питания. Создадим пустой датафрейм top10_adresses_districts с заголовками.

In [87]:
top10_adresses_districts = pd.DataFrame(
    columns=['district_zelao', 'district_szao', 'district_zao', 'district_uzao', 'district_uao', \
             'district_uvao', 'district_vao', 'district_svao', 'district_sao', 'district_cao']
)
top10_adresses_districts
Out[87]:
district_zelao district_szao district_zao district_uzao district_uao district_uvao district_vao district_svao district_sao district_cao

В цикле по каждой улице найдём в таблице rest_data первое совпадение по улице и добавим информацию по округам.

In [88]:
for i in top10_adresses['index']:
    top10_adresses_districts = top10_adresses_districts.append(
        rest_data[rest_data['address_fixed'] == i].iloc[:, 8:18].head(1), ignore_index=True)
In [89]:
top10_adresses_districts
Out[89]:
district_zelao district_szao district_zao district_uzao district_uao district_uvao district_vao district_svao district_sao district_cao
0 NaN NaN ЗАО NaN NaN NaN NaN СВАО NaN ЦАО
1 NaN NaN NaN ЮЗАО NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN САО NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN ЦАО
4 NaN NaN NaN ЮЗАО ЮАО NaN NaN NaN NaN NaN
5 NaN NaN ЗАО ЮЗАО ЮАО NaN NaN NaN NaN ЦАО
6 ЗелАО NaN NaN NaN NaN NaN NaN NaN NaN NaN
7 NaN NaN ЗАО ЮЗАО NaN NaN NaN NaN NaN NaN
8 NaN NaN ЗАО NaN NaN NaN NaN NaN NaN NaN
9 NaN NaN NaN NaN ЮАО NaN NaN NaN NaN NaN

Посчитаем количество округов, добавим названия и отсортируем по убыванию.

In [90]:
top10_adresses_districts = top10_adresses_districts.count().reset_index()
top10_adresses_districts.columns = ['id', 'streets']
top10_adresses_districts['district'] = pd.Series(
    ['ЗелАО', 'СЗАО', 'ЗАО', 'ЮЗАО', 'ЮАО', 'ЮВАО', 'ВАО', 'СВАО', 'САО', 'ЦАО'])
top10_adresses_districts = top10_adresses_districts.sort_values(by='streets', ascending=False)
In [91]:
top10_adresses_districts
Out[91]:
id streets district
2 district_zao 4 ЗАО
3 district_uzao 4 ЮЗАО
4 district_uao 3 ЮАО
9 district_cao 3 ЦАО
0 district_zelao 1 ЗелАО
7 district_svao 1 СВАО
8 district_sao 1 САО
1 district_szao 0 СЗАО
5 district_uvao 0 ЮВАО
6 district_vao 0 ВАО

Построим график.

In [92]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Топ-10 популярных округов')
sns.barplot(x="district", y="streets", data=top10_adresses_districts)
ax = plt.gca()
ax.set_ylabel('Количество объектов общественного питания')
ax.set_xlabel('Округа');

Вывод: больше всего объектов общественного питания в Центральном, Западном, Юго-Западном и Южном округах города Москвы.

Сравним с ценами на недвижимость по данным сайта: https://www.irn.ru/rating/moscow/?segment=areas&param=IS&sort=value&currency=0

Центральный, Юго-Западный и Западный округи самые дорогие. На четвёртом месте Северо-Западный вместо Южного. Связано ли количество объектов общественного питания в Южном округе с тем, что он является крупнейшим по численности населения?

Найдём число улиц с одним объектом общественного питания.

Сохраним в переменную not_popular_streets количество уникальных значений адресов методом value_counts(), сбросим индексы методом reset_index(), изменим заголовки, выберем улицы с одним объектом общественного питания.

In [93]:
not_popular_streets = rest_data['address_fixed'].value_counts().reset_index()
not_popular_streets.columns = ['street', 'number']
not_popular_streets = not_popular_streets.query('number == 1')
not_popular_streets.head()
Out[93]:
street number
1409 Штурвальная улица 1
1410 Чуксин тупик 1
1411 Русаковская набережная 1
1412 поселение "Мосрентген", Киевское шоссе, 21-й к... 1
1413 Боровский проезд 1

Найдём, в каких районах Москвы находятся улицы с одним объектом общественного питания. Создадим пустой датафрейм not_popular_streets_districts с заголовками.

In [94]:
not_popular_streets_districts = pd.DataFrame(
    columns=['district_zelao', 'district_szao', 'district_zao', 'district_uzao', 'district_uao', \
             'district_uvao', 'district_vao', 'district_svao', 'district_sao', 'district_cao']
)
not_popular_streets_districts
Out[94]:
district_zelao district_szao district_zao district_uzao district_uao district_uvao district_vao district_svao district_sao district_cao

В цикле по каждой улице найдём в таблице rest_data первое совпадение по улице и добавим информацию по округам.

In [95]:
for i in not_popular_streets['street']:
    not_popular_streets_districts = not_popular_streets_districts.append(
        rest_data[rest_data['address_fixed'] == i].iloc[:, 8:18].head(1), ignore_index=True)
In [96]:
not_popular_streets_districts.head()
Out[96]:
district_zelao district_szao district_zao district_uzao district_uao district_uvao district_vao district_svao district_sao district_cao
0 NaN СЗАО NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN САО NaN
2 NaN NaN NaN NaN NaN NaN ВАО NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN ЗАО NaN NaN NaN NaN NaN NaN NaN

Посчитаем количество округов, сбросим индексы, изменим заголовки, добавим названия и отсортируем по убыванию.

In [97]:
not_popular_streets_districts = not_popular_streets_districts.count().reset_index()
not_popular_streets_districts.columns = ['id', 'streets']
not_popular_streets_districts['district'] = pd.Series(
    ['ЗелАО', 'СЗАО', 'ЗАО', 'ЮЗАО', 'ЮАО', 'ЮВАО', 'ВАО', 'СВАО', 'САО', 'ЦАО'])
not_popular_streets_districts = not_popular_streets_districts.sort_values(by='streets', ascending=False)
In [98]:
not_popular_streets_districts
Out[98]:
id streets district
9 district_cao 179 ЦАО
7 district_svao 67 СВАО
6 district_vao 64 ВАО
5 district_uvao 57 ЮВАО
8 district_sao 53 САО
2 district_zao 46 ЗАО
4 district_uao 32 ЮАО
1 district_szao 28 СЗАО
3 district_uzao 28 ЮЗАО
0 district_zelao 1 ЗелАО

Построим график.

In [99]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Топ-10 непопулярных округов')
sns.barplot(x="district", y="streets", data=not_popular_streets_districts)
ax = plt.gca()
ax.set_ylabel('Количество объектов общественного питания')
ax.set_xlabel('Округа');

Вывод: самыми непопулярными являются Северо-Восточный, Восточный, Юго-Восточный и Северный округа.

Примечание: исключил Центральный округ, потому что там больше всего объектов общественного питания, стало быть, больше всего улиц с одним объектом общественного питания.

Посмотрим на распределение количества посадочных мест для улиц с большим количеством объектов общественного питания.

Для этого создадим пустой объект Series top_10_numbers. В цикле по каждой из топ-10 улиц найдём в таблице rest_data совпадение по улице и добавим количество посадочных мест в top_10_numbers.

In [100]:
top_10_numbers = pd.Series()
top_10_numbers
Out[100]:
Series([], dtype: float64)
In [101]:
for i in top10_adresses['index']:
    top_10_numbers = top_10_numbers.append(rest_data[rest_data['address_fixed'] == i].loc[:, 'number'])

Построим гистограмму, ограничив количество посадочных мест 250.

In [102]:
plt.figure(figsize=(12, 7))
plt.grid(True)
plt.title('Распределение количества посадочных мест для улиц с большим количеством объектов общественного питания')
ax = plt.gca()
ax.set_xlabel('Количество мест')
ax.set_ylabel('Количество заведений')
top_10_numbers[top_10_numbers < 250].hist(figsize=(12, 7), bins=100);

Вывод: для улиц с большим количеством объектов общественного питания характерно небольшое число посадочных мест (до 50).

Общие выводы

Налицо географическая стратификация Москвы: элитный Центр, богатые Запад и Юго-Запад, бедные Северо-Восток, Восток и Юго-Восток, средние Север, Северо-Запад, Юг.

https://newizv.ru/news/city/01-08-2019/novaya-karta-moskvy-pokazala-sotsialnoe-rassloenie-zhiteley

Самыми популярными объектами общественного питания в Москве являются кафе, столовые, рестораны и фастфуд. Наименее популярными — кафетерии, закусочные и отделы кулинарии в магазинах.

Несетевых заведений в Москве в четыре раза больше, чем сетевых.

Больше всего сетей кафе, фастфудов, ресторанов.

Для большинства сетевых заведений характерно небольшое число посадочных мест (до 50).

Больше всего объектов общественного питания в Центральном, Западном, Юго-Западном и Южном округах города Москвы.

Самыми непопулярными являются Северо-Восточный, Восточный, Юго-Восточный и Северный округа.

Для улиц с большим количеством объектов общественного питания характерно небольшое число посадочных мест (до 50).

Рекомендации: кафе, ресторан, фастфуд с небольшим числом посадочных мест (до 50) в Центральном, Западном, Юго-Западном округах.